home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Commodore Free 31
/
Commodore_Free_Issue_31_2009_Commodore_Computer_Club.d64
/
c part 2.2
< prev
next >
Wrap
Text File
|
2023-02-26
|
17KB
|
666 lines
u
Alternative Programming Languages: C
Part 2
By Paul Davis
CONTINUED FROM PART 2.1
This program still does nothing. I
just want to demonstrate what happens
when a change is made to a file. Save
the file and enter the make command:
make
The make command will run through all
the dependencies and determine that
the '.c' source file has changed so
it needs to run the cc65 command to
compile it. Then, since the '.s'
assembler source has now changed, it
needs to run the ca65 command to
assemble it. Then, since the '.o'
object file has now changed it needs
to run the ld65 linker to create the
executable. The makefile has helped
us to remove some of the complexity
of using the separate compiler tools.
Now, whenever you make a change to
your program, just issue the 'make'
command and it is automatically
compiled and linked.
Makefiles have many other features
that can simplify the compilation
process even further. These features
are beyond the scope of this article.
I will leave it as an exercise for
the reader to investigate this in
more detail.
Creating the function library
Finally, we're armed with the
knowledge and tools required to
create our sprite handling function
library. So let's get started. First,
we should start from the point of
having a working program that we will
gradually change to use our library
functions. Edit the 'sprtst.c' file
and change it to the following:
#include <string.h>
#include <peekpoke.h>
#include <c64.h>
#include "mytypes.h"
#define SPRITE0_DATA 0x0340
#define SPRITE0_PTR 0x07F8
void main(void)
memset((void*)SPRITE0_DATA,0xff,64);
POKE(SPRITE0_PTR, SPRITE0_DATA/64);
VIC.spr0_colour = COLOR_WHITE;
VIC.spr_hi_x = 0;
VIC.spr0_x = 100;
VIC.spr0_y = 100;
VIC.spr_ena = 1;
Compile it (using the 'make' command)
and run the 'sprtst' program in VICE
to check that it works.
Now we have a working program, we can
write and test functions in our
library one at a time by replacing
the lines in this program with calls
to the new function. This incremental
approach: write a bit, test a bit, is
a good way to build a library,
especially when you're new to this
kind of development.
It's always a good idea to start with
a little planning. We need to decide
what features we want the library to
provide for us. It's easy to get
carried away and put anything and
everything in there. Some thought at
the beginning can help to focus our
efforts.
For our example library we will keep
things simple. We want to be able to
show and hide sprites, change their
colour and move them anywhere on the
screen. We also want to be able to
use all 8 sprites, not just one. This
last requirement gives us a clue that
many of the library functions will
need to take a sprite number as a
parameter.
Okay, let's start with one of the
easier functions, the one that
enables a particular sprite and shows
it on the screen. In our test program
above we simply put the number 1 into
VIC.spr_ena, but our requirement to
use all the sprites means we need to
do more than this.
The 'sprite enable' register of the
VIC chip is actually a bit-mapping.
In other words, each bit of the
number stored there corresponds to
the setting for one sprite. Since
there are 8 sprites and 8 bits in a
byte, this fits nicely. The question
is, how do we get to a particular
bit?
The C language has a convenient way
of doing this using the 'shift left'
operator. The expression '1 << n'
where 'n' is the sprite number will
generate the correct value. It's
beyond the scope of this article to
explain in detail why this is, so for
now, just take it on faith that it
works.
With this knowledge, we can now make
an attempt at a function to enable
any sprite. Create a new file called
'sprlib.c' and enter this code into
it:
#include <c64.h>
#include "sprlib.h"
void spr_show(byte spr)
VIC.spr_ena = 1 << spr;
We have called the function spr_show.
It's usually a good idea to prefix
the names of functions in a library
with the same few characters to
identify which library they belong to
and avoid name collisions with
functions in other libraries. Here we
are using 'spr' for our sprite
library.
The function takes a sprite number as
a parameter and uses the expression
'1 << spr' to calculate the bit value
for that sprite. It then uses the
'=' operator to combine this bit
value with the value that is already
in the register. If we just assigned
the value with '=' it would turn off
all the other sprites except the one
we want to show. The '' symbol is
the 'bitwise OR' operator in C.
Next, we need to provide a way for
other programs to call our function.
We do this by creating a header file
for those programs to include. Create
a new file called 'sprlib.h' and
enter these lines:
#include <c64.h>
#include "mytypes.h"
void spr_show(byte spr);
The declaration of the spr_show
function here is called a 'function
prototype'. It just lists the return
type, name and parameters of the
function. Notice that the prototype
doesn't have any code after it in
curly braces and that the line ends
with a semi-colon. Prototypes are C's
way of telling other programs that a
function exists and what its return
type and parameters are.
Next, in the 'sprtst.c' file, change
the #include "mytypes.h" line to
read:
#include "sprlib.h"
and change the line that reads:
VIC.spr_ena = 1;
to use our new function instead:
spr_show(0);
Now we need to change our 'makefile'
to add the instructions to compile
the library. Change 'makefile' to
look like the text below. Remember to
indent the commands with a tab, not
spaces (the ld65 command should be
entered all on one line, even though
it has wrapped around in the magazine
listing):
sprtst: sprtst.o spr.lib
ld65 -t c64 -o sprtst c64.o
sprtst.o spr.lib c64.lib
sprtst.o: sprtst.s
ca65 -t c64 sprtst.s
sprtst.s: sprtst.c sprlib.h
cc65 -t c64 sprtst.c
spr.lib: sprlib.o
ar65 a spr.lib sprlib.o
sprlib.o: sprlib.s
ca65 -t c64 sprlib.s
sprlib.s: sprlib.c sprlib.h mytypes.h
cc65 -t c64 sprlib.c
This new makefile contains
instructions to compile the sprlib.c
file and contains a new command
'ar65' that puts object files into a
library file. Our sprite library is
called 'spr.lib'. The entry for
'sprtst' has also been changed to add
spr.lib as a dependency and as part
of the 'ld65' link instruction.
Compile these changes by running the
'make' command. If all goes well, it
should compile sprlib, create the
library and compile sprtst, linking
with the new library. Load and run
this program in VICE to check it
still works.
Next, we will create the function to
change the sprite colour. This will
need to take two parameters, the
sprite number and the colour value.
Add the following lines to the end of
'sprlib.c':
void spr_colour(byte spr, byte
colour)
(&VIC.spr0_colour)[spr] = colour;
The use of the ampersand in this
function needs some explanation. The
VIC chip registers for sprite colours
are conveniently located together in
memory. The colour for sprite 1
immediately follows that for sprite
0. We can take advantage of this
arrangement and treat those registers
like an array of bytes. The only
problem is the variable
VIC.spr0_colour is of type 'byte' so
we can't treat it directly as an
array.
In C, an 'array' type is nothing more
than a pointer to the memory address
of the first element. It follows
therefore, that if we can get the
memory address of the VIC.spr0_colour
variable then we can treat it as an
array. This is exactly what the
ampersand operator is used for here.
An ampersand returns the memory
address of the variable name that
follows it. Once we have that
address, we can simply use the
familiar square brackets as an index
into this 'array'.
As before, we need to add a function
prototype to the end of 'sprlib.h':
void spr_colour(byte spr,
byte colour);
In the test program, 'sprtst.c', edit
the line that changes the sprite
colour to use the new function:
spr_colour(0, COLOR_WHITE);
Compile all the changes by issuing
the 'make' command and run the
program in VICE to test it still
works.
Let's keep up the momentum and create
a function to set a sprite pointer.
Enter this code into 'sprlib.c':
byte *sprite_pointers =
(byte*)0x07F8;
void spr_ptr(byte spr, byte ptr)
sprite_pointers[spr] = ptr;
For the moment, we have just
hard-coded the start address of the
sprite pointers in memory. It is
possible, and preferable, to use the
VIC registers to calculate what this
address should be. We will see how to
do this later.
Add this prototype to the 'sprlib.h'
file:
void spr_ptr(byte spr, byte ptr);
Finally, change the line in
'sprtst.c' that pokes the sprite
pointer to use the new function:
spr_ptr(0, SPRITE0_DATA / 64);
Compile with 'make' and test in VICE
as usual to check it still works. By
now you should be starting to
appreciate the benefit of the make
tool. The effort put in setting it up
at the start is now being paid back
many times over. Making a change to
the library is very simple and we
have got into a good rhythm of
editing the source files, compiling
and testing.
The function to set the sprite
pointer is okay, but it still makes
the caller calculate the pointer
value from the real address. We could
change the function, or write a new
one, that takes an address rather
than a pointer but there are times
when pointer numbers are the better
fit, animation being the prime
example. Our library would be more
useful if it provided a way to
calculate the pointer number from an
address. We could write a function to
do this but C provides another way,
macros. Enter this line into the
'sprlib.h' header file (all on one
line):
#define SPR_PTR(addr)
(((addr) / 64) & 0xff)
In the 'sprtst.c' file change the
line that sets the pointer to this:
spr_ptr(0, SPR_PTR(SPRITE0_DATA));
We have seen the #define instruction
used to create constants before. Here
it is being used to create a macro.
To the program using it, a macro
looks just like a function. But
unlike a function, it is not called.
Instead, the macro is literally
replaced by its text value. To
demonstrate, let's see what the
compiler would do with the spr_ptr
call above.
The compiler recognises
SPR_PTR(SPRITE0_DATA) as a macro and
expands it using the text in the
original definition. The resulting
instruction would look like this:
spr_ptr(0,(((SPRITE0_DATA)/64)&0xff));
The value SPRITE0_DATA is also a
defined constant so this too will be
replaced with its literal value. The
command now looks like this:
spr_ptr(0,(((0x0340)/64) & 0xff));
The compiler is clever enough to
realise that because this expression
uses literal values it can simplify
it by calculating the result. The
command now looks like this:
spr_ptr(0, 13);
This is the major benefit of using
macros instead of functions for some
tasks. They can help to optimise the
code, especially when used with
constant expressions and values.
The last function that we need to
write to finish our test program
moves a sprite to any position on the
screen. Enter this function code into
'sprlib.c':
void spr_move(byte spr, int x, int y)
byte index = 2 * spr;
(&VIC.spr0_x)[index] = x & 0xff;
(&VIC.spr0_y)[index] = y;
if (x & 0x100)
VIC.spr_hi_x = 1 << spr;
else VIC.spr_hi_x &= (1 << spr);
This new function is a bit more
complicated than those we have
previously written, but it uses
several of the same ideas. Part of
this complexity is caused by the
layout of the sprite position
registers in memory. Unlike the
colour registers which are
conveniently grouped together, the
position registers are grouped in
pairs: sprite0_x, sprite0_y,
sprite1_x, sprite1_y and so on. This
means that we can't use the sprite
number directly as an index to an
'array' of registers. The function
needs to multiply the sprite number
by 2 to get the array index because
there are groups of two registers per
sprite.
The spr_move function has also
introduced a couple of new operators.
The tilde character '' is the
bitwise NOT operator in C and the
ampersand is the bitwise AND
operator. Together they are used to
turn off a particular bit in a value.
At some point in the future I'm
hoping to write an article that
describes in more detail how these
'bit manipulations' work. For the
purposes of this article however,
suffice it to say that to turn a bit
on, use an expression like this:
variable = 1 << bit
And to turn a bit off, use an
expression like this:
variable &= (1 << bit)
We now need to add the prototype for
this function into 'sprlib.h':
void spr_move(byte spr, int x, int
y);
And replace these lines in
'sprtst.c':
VIC.spr_hi_x = 0;
VIC.spr0_x = 100;
VIC.spr0_y = 100;
with a call to the new function:
spr_move(0, 100, 100);
Compile these changes with 'make' and
test the program in VICE as usual.
At this point we should take the
opportunity to tidy up the test
program. There are a couple of
include files and the definition of
the sprite pointer address that are
no longer needed. Remove these lines
from 'sprtst.c':
#include <peekpoke.h>
#include <c64.h>
#define SPRITE0_PTR 0x07F8
Compile again with 'make' to check it
still compiles.
To finish, let's add a couple of new
functions. One will calculate the
address of the sprite pointers using
the VIC registers instead of
hard-coding the value into the
library. The other will allow a
sprite to be expanded. Enter this
code into 'sprlib.c':
void spr_init(void)
sprite_pointers = (byte*)
((((CIA2.pra & 3) ^ 3) * 0x4000)
((VIC.addr & 0xf0) * 0x40)
+ 0x03f8);
void spr_expand(byte spr,
bool expandx, bool expandy)
if (expandx)
VIC.spr_exp_x = 1 << spr;
else VIC.spr_exp_x &= (1 << spr);
if (expandy)
VIC.spr_exp_y = 1 << spr;
else VIC.spr_exp_y &= (1 << spr);
The expression in the spr_init
function isn't quite as scary as it
looks. It's basically split into two
parts, the first half works out which
VIC bank of memory is being used and
multiplies this by 0x4000 to get the
base memory address for that bank.
The second half of the expression
reads the VIC register that controls
where in memory the text screen is
located and uses that to calculate
where the sprites are within the VIC
bank. Combining the two halves of the
expression gives the actual memory
address of the sprite pointers.
Next, enter the function prototypes
into 'sprlib.h':
void spr_init(void);
void spr_expand(byte spr,
bool expandx, bool expandy);
Now, let's do something a bit more
with our new functions and show three
sprites. Change 'sprtst.c' to look
like this:
#include <string.h>
#include "sprlib.h"
#define SPR_DATA 0x0340
#define SPR0_DATA (SPR_DATA+0x00)
#define SPR1_DATA (SPR_DATA+0x40)
#define SPR2_DATA (SPR_DATA+0x80)
void main(void)
memset((void*)SPR0_DATA, 0xff, 64);
memset((void*)SPR2_DATA, 0xcc, 64);
memset((void*)SPR1_DATA, 0xaa, 64);
spr_init();
spr_ptr(0, SPR_PTR(SPR0_DATA));
spr_ptr(1, SPR_PTR(SPR1_DATA));
spr_ptr(2, SPR_PTR(SPR2_DATA));
spr_colour(0, COLOR_YELLOW);
spr_colour(1, COLOR_GREEN);
spr_colour(2, COLOR_BLACK);
spr_move(0, 290, 100);
spr_move(1, 280, 110);
spr_move(2, 270, 120);
spr_expand(0, TRUE, FALSE);
spr_expand(1, FALSE, TRUE);
spr_expand(2, TRUE, TRUE);
spr_show(0);
spr_show(1);
spr_show(2);
Compile the program with 'make' and
run it in VICE to see the results.
Over to you
In the limited space of this article
we haven't been able to create a
complete library. There are several
other functions that would be useful
additions. I will leave this as an
exercise for the reader to implement
some of these missing functions.
To get you started with some ideas,
how about writing a function to hide
a particular sprite and one to hide
all sprites? You could also try
writing a function to set the
multicolour mode of a sprite and
functions to set the corresponding
multicolour registers.
An implementation of these functions,
plus a few more, can be found in the
zip file that accompanies this
article on my website, listed below.
Final words
Last time, I mentioned that this
article would show how to implement a
function in assembly language.
Unfortunately, there wasn't enough
space to include that content here.
If there is enough demand I will
write a third part to this series
focussing on optimisation techniques
and assembly language integration.
Please write in to the magazine or
post a comment on my blog if you
would be interested in reading about
this.
All my articles and their associated
resources are now available at my web
site which can be found at
http://sites.google.com/site/
develocity/commodore/articles.
Or leave a comment on any of the
articles at my blog which is at
http://retrodev.blogspot.com/
--